#include "src/ltini.h"

#include <stdbool.h>
#include <stdint.h>
#include <string.h>

#include "src/lta.h"
#include "src/ltbase.h"
#include "src/ltdstr.h"
#include "src/lterr.h"
#include "src/ltlog.h"
#include "src/ltt.h"
#include "src/lttry.h"

lt_UsingType(lt, USize);
lt_UsingType(ltio, Rune);
lt_UsingType(lterr, Code);
lt_UsingType(ltdstr, String);

static Code
ReadUntil(void* reader_ctx, ltio_IReadChar reader, lta_Chars* chars,
          char target) {
    for (;;) {
        Rune c = reader(reader_ctx);
        if (c < 0)
            return -c;
        lta_Append(*chars, (char)c);
        if (c == target)
            return lterr_kOk;
    }
}

static Code
ReadLine(void* reader_ctx, ltio_IReadChar reader, lta_Chars* chars) {
    Code err = ReadUntil(reader_ctx, reader, chars, '\n');
    lta_Append(*chars, '\0');
    return err;
}

static bool
IsWhitespace(char c) {
    switch (c) {
        case '\t':
        case '\n':
        case '\r':
        case ' ': return true;
    }
    return false;
}

/** 将最后一个空白字符置为0，返回新字符串的长度。*/
static USize
TrimWhitespaceEnd(char* string, USize len) {
    while (len > 0 && IsWhitespace(string[len - 1])) {
        len -= 1;
    }
    string[len] = 0;
    return len;
}

/** 返回第一个不是空白的字符的位置。

示例：

    char *string = "   abc";
    USize offset = TrimWhitespaceStart(buf);
    assert(strcmp(string + offset, "abc"));
*/
static USize
TrimWhitespaceStart(char const* string) {
    USize offset = 0;
    while (string[offset] != 0 && IsWhitespace(string[offset])) {
        offset += 1;
    }
    return offset;
}

static USize
StringFindChar(char const* text, char target) {
    USize len = 0;
    for (; text[len] != 0; ++len) {
        if (text[len] == target)
            return len;
    }
    return len;
}

static void
Escape(char const* text, USize len, lta_Chars* out) {
    out->len = 0;

    bool escape = false;
    for (USize i = 0; i < len; ++i) {
        char c = text[i];

        if (escape) {
            switch (c) {
                case 't': lta_Append(*out, '\t'); break;
                case 'r': lta_Append(*out, '\r'); break;
                case 'n': lta_Append(*out, '\n'); break;
                default: lta_Append(*out, c); break;
            }
            escape = false;
            continue;
        }

        if (c == '\\') {
            escape = true;
            continue;
        }

        lta_Append(*out, c);
    }

    lta_Append(*out, 0);
}

typedef struct {
    char const* content;

    USize pos;
    /* 从0开始的行号。*/
    USize line;
} Parser;

static Rune
Peek(Parser* p) {
    return (Rune)((unsigned char)p->content[p->pos]);
}

static Rune
Next(Parser* p) {
    Rune r = Peek(p);
    if (r != 0) {
        p->pos += 1;
        if (r == '\n')
            p->line += 1;
    }
    return r;
}

static void
SkipWs(Parser* p) {
    while (IsWhitespace(Peek(p))) {
        Next(p);
    }
}

static Code
TryNextPair(Parser* p, String* key, String* value) {
    SkipWs(p);

    ltdstr_Clear(key);
    for (;;) {
        Rune c = Next(p);
        if (c == '\n' || c == 0) {
            ltlog_Log1(Error, "reach line end before end of pair, missing '='",
                       "line", USize(p->line));
            return lterr_kError;
        }

        if (IsWhitespace(c)) {
            SkipWs(p);

            if ((c = Next(p)) != '=') {
                ltlog_Log1(Error, "a key contains whitespace", "line",
                           USize(p->line));
                return lterr_kError;
            }
            break;
        }

        if (c == '=')
            break;

        ltdstr_AppendChar(key, c);
    }

    SkipWs(p);

    ltdstr_Clear(value);
    for (;;) {
        Rune c = Next(p);
        if (IsWhitespace(c) || c == 0)
            break;
        ltdstr_AppendChar(value, c);
    }

    return lterr_kOk;
}

static Code
TryNextSection(Parser* p, String* section) {
    SkipWs(p);

    if (Next(p) != '[') {
        return lterr_kError;
    }

    ltdstr_Clear(section);

    for (;;) {
        Rune c = Next(p);
        if (c == '\n' || c == 0) {
            ltlog_Log1(Error, "reach line end before end of section", "line",
                       USize(p->line));
            return lterr_kError;
        }

        if (c == ']')
            break;

        ltdstr_AppendChar(section, c);
    }

    for (;;) {
        Rune c = Next(p);

        if (c == '\n' || c == 0)
            break;

        if (!IsWhitespace(c)) {
            ltlog_Log2(Error,
                       "extra non-whitespace character after section end",
                       "line", USize(p->line), "exter char", CharQuote(c));
            return lterr_kError;
        }
    }

    return lterr_kOk;
}

typedef struct {
    USize pos;
    USize line;
} SavePoint;

void
Save(Parser* p, SavePoint* sp) {
    sp->pos = p->pos;
    sp->line = p->line;
}

void
Load(Parser* p, SavePoint* sp) {
    p->pos = sp->pos;
    p->line = sp->line;
}

static Code
NextSection(Parser* p, String* section) {
    SavePoint sp;

    Save(p, &sp);

    Code code = TryNextSection(p, section);
    if (code != lterr_kOk) {
        Load(p, &sp);
    }

    return code;
}

static Code
PeekSection(Parser* p, String* section) {
    SavePoint sp;

    Save(p, &sp);
    Code code = TryNextSection(p, section);
    Load(p, &sp);

    return code;
}

static Code
ExpectSection(String* section, char const* expect_section) {
    if (strcmp(ltdstr_Get(section), expect_section) != 0) {
        ltlog_Log0(Error, "invalid section");
        return lterr_kError;
    }
    return lterr_kOk;
}

ltt_Test(ltini_Section) {
    {
        Parser p = {.content = " \t[abc]"};
        String section = {0};
        lttry_Try(PeekSection(&p, &section));
        lttry_Try(ExpectSection(&section, "abc"));
    }

    {
        Parser p = {.content = "[abc]  "};
        String section = {0};
        lttry_Try(PeekSection(&p, &section));
        lttry_Try(ExpectSection(&section, "abc"));
    }

    {
        Parser p = {.content = "[abc]"};
        String section = {0};

        for (USize i = 0; i < 3; ++i) {
            lttry_Try(PeekSection(&p, &section));
            lttry_Try(ExpectSection(&section, "abc"));
        }
    }

    return lterr_kOk;
}

static Code
Parse(void* reader_ctx, ltio_IReadChar reader, void* visitor_ctx,
      ltini_IVisitor visitor, lta_Chars* line_buf, lta_Chars* section_buf,
      lta_Chars* value_buf) {
    Code ioerr;
    int line_num = 1;
    char const* section = "";

    for (bool end = false; !end; line_num += 1) {
        line_buf->len = 0;

        ioerr = ReadLine(reader_ctx, reader, line_buf);
        if (ioerr == lterr_kEnd) {
            end = true;
        } else if (ioerr != 0) {
            return ioerr;
        }

        char* line = line_buf->data;
        USize line_len = line_buf->len - 1;

        line_len = TrimWhitespaceEnd(line, line_len);
        USize offset = TrimWhitespaceStart(line);
        line += offset;
        line_len -= offset;

        /* 跳过空行和注释 */
        if (line_len == 0 || line[0] == '#')
            continue;

        if (line[0] == '[') {
            if (line[line_len - 1] != ']')
                return lterr_kEnd;

            section_buf->len = 0;
            for (USize i = 1; i < line_len - 1; ++i) {
                lta_Append(*section_buf, line[i]);
            }
            lta_Append(*section_buf, 0);

            section = section_buf->data;
            continue;
        }

        USize eq = StringFindChar(line, '=');
        if (line[eq] == 0)
            return lterr_kError;

        line[eq] = 0;

        char* key = line;
        TrimWhitespaceEnd(key, eq);

        char* value = line + eq + 1;
        USize value_len = line_len - (eq + 1);

        offset = TrimWhitespaceStart(value);
        value_len -= offset;
        value += offset;

        if (value[0] == '"') {
            if (value[value_len - 1] != '"')
                return lterr_kError;
            Escape(value + 1, value_len - 2, value_buf);
            value = value_buf->data;
        }

        int r = visitor(visitor_ctx, line_num, section, key, value);
        if (r != 0)
            return r;
    }

    return lterr_kOk;
}

Code
ltini_Parse(void* reader_ctx, ltio_IReadChar reader, void* visitor_ctx,
            ltini_IVisitor visitor) {
    lta_Chars line_buf = {0};
    lta_Chars section_buf = {0};
    lta_Chars value_buf = {0};

    Code code = Parse(reader_ctx, reader, visitor_ctx, visitor, &line_buf,
                      &section_buf, &value_buf);

    lta_DeInit(line_buf);
    lta_DeInit(section_buf);
    lta_DeInit(value_buf);

    return code;
}
